/*
Copyright (C) 2004 Geoffrey Alan Washburn
   
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
   
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
   
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
USA.
*/
  
//import javax.swing.JFrame;
//import javax.swing.JScrollPane;
//import javax.swing.JTextPane;
//import javax.swing.JPanel;
//import javax.swing.JTable;
//import javax.swing.JOptionPane;
import java.awt.*;
import java.awt.event.*;
//import java.awt.GridBagConstraints;
import javax.swing.*;
import java.io.*;
import java.util.*;
//import java.net.InetAddress;
import java.net.*;
import java.net.ServerSocket;
import java.util.Vector;

/**
 * The entry point and glue code for the game.  It also contains some helpful
 * global utility methods.
 * @author Geoffrey Washburn &lt;<a href="mailto:geoffw@cis.upenn.edu">geoffw@cis.upenn.edu</a>&gt;
 * @version $Id: Mazewar.java 371 2004-02-10 21:55:32Z geoffw $
 */

public class Mazewar extends JFrame implements KeyListener
{


	 /**
         * The default width of the {@link Maze}.
     */
        private final int mazeWidth = 20;

    /**
         * The default height of the {@link Maze}.
     */
        private final int mazeHeight = 10;

    /**
         * The default random seed for the {@link Maze}.
     * All implementations of the same protocol must use 
     * the same seed value, or your mazes will be different.
     */
        private final int mazeSeed = 42;

    /**
         * The {@link Maze} that the game uses.
     */
        public Maze maze = null;

    /**
         * The {@link GUIClient} for the game.
     */
        private GUIClient guiClient = null;

    /**
         * The panel that displays the {@link Maze}.
     */
        private OverheadMazePanel overheadPanel = null;

    /**
         * The table the displays the scores.
     */
        private JTable scoreTable = null;

    /** 
         * Create the textpane statically so that we can 
     * write to it globally using
     * the static consolePrint methods  
     */
        private static final JTextPane console = new JTextPane();

    //Centralized server port and hostName
    static String hostName = "";
    static String clientName = "";
    static int portNum = -1;
  
    public Hashtable<Peer, Client> clientList;
  

    ServerSocket _ListenSocket = null;
    boolean keepListening;
    Peer myself;
    Vector peers;
	

   

    /** 
         * Write a message to the console followed by a newline.
     * @param msg The {@link String} to print.
     */
    public static synchronized void consolePrintLn(String msg)
    {
        console.setText(console.getText() + msg + "\n");
    }

    /** 
         * Write a message to the console.
         * @param msg The {@link String} to print.
     */
    public static synchronized void consolePrint(String msg)
    {
        console.setText(console.getText() + msg);
    }

    /** 
         * Clear the console. 
         */
    public static synchronized void clearConsole()
    {
        console.setText("");
    }

   

	public Point getLoc()
	{
		Point loc = maze.getClientPoint(guiClient);
		return loc;
	}

	public Direction getDir()
	{
		Direction dir = maze.getClientOrientation(guiClient);
		return dir;
	}


	/**
	 * Handle a key press.
	 * @param e The {@link KeyEvent} that occurred.
	 */
	public void keyPressed(KeyEvent e)
	{
		// If the user pressed Q, invoke the cleanup code and quit. 
		if ((e.getKeyChar() == 'q') || (e.getKeyChar() == 'Q'))
		{
            try
            {
                keepListening = false;
                broadcast_bye();
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            System.exit(0);
		}
		else
		{
		//	System.out.println("Other key than q");
			sentToPeers(e);
		}
	}

	public void sentToPeers(KeyEvent e)
	{
		//System.out.println("Event received!!");
		GamePacket packetToPeer = new GamePacket();
		packetToPeer.MsgType = GamePacket.MessageType.UPDATE;

		if (e.getKeyCode() == KeyEvent.VK_UP)
		{
			packetToPeer.MsgAction = GamePacket.Action.UP;
			guiClient.forward();
		}
		else if (e.getKeyCode() == KeyEvent.VK_DOWN)
		{
			packetToPeer.MsgAction = GamePacket.Action.DOWN;
			guiClient.backup();
		}
		else if (e.getKeyCode() == KeyEvent.VK_LEFT)
		{
			packetToPeer.MsgAction = GamePacket.Action.LEFT;
			guiClient.turnLeft();
		}
		else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
		{
			packetToPeer.MsgAction = GamePacket.Action.RIGHT;
			guiClient.turnRight();
		}
		else if (e.getKeyCode() == KeyEvent.VK_SPACE)
		{
			packetToPeer.MsgAction = GamePacket.Action.FIRE;

		}     
		for (int i = 0; i < peers.size(); i++)
		{
			Peer p = (Peer)peers.get(i);
		
			if (p == myself)
			{
				continue;
			}

			packetToPeer.me = myself;

	
			try
			{
				Socket peerSocket = new Socket(p.hostname, p.port);
				ObjectOutputStream out = new ObjectOutputStream(peerSocket.getOutputStream());
				out.writeObject(packetToPeer);
				out.close();
				peerSocket.close();				
			}
			catch (Exception exp)
			{ }
		}

	}

	/**
		* Handle a key release. Not needed by {@link GUIClient}.
		* @param e The {@link KeyEvent} that occurred.
		*/
	public void keyReleased(KeyEvent e)
	{
	}

	/**
		* Handle a key release. Not needed by {@link GUIClient}.
		* @param e The {@link KeyEvent} that occurred.
		*/
	public void keyTyped(KeyEvent e)
	{
	}

    public void add_remote_client(GamePacket packet)
    {        	
        peers.add(packet.me);        
		RemoteClient newClient = new RemoteClient(packet.me.name);
        Point p = new Point(packet.locationX, packet.locationY);
		Direction dir = new Direction(packet.direction);
		maze.addClientAtGivenPoint(newClient, new DirectedPoint(p, dir));				
        clientList.put(packet.me, newClient);        
    }
    
    public void vaporize_remote_client(GamePacket packet)
    {
    	System.out.println("packet says target is " + packet.targetName);
    	boolean found = false;
    	int peerLoc = 0;
    	for (int i = 0; i < peers.size() && !found; i++)
		{
			Peer p = (Peer)peers.get(i);
			if(p.name.equals(packet.targetName))
			{
				System.out.println("Target is " + packet.targetName);
				found = true;
				peerLoc = i;
				break;
			}
		}
				
    	remove_remote_client((Peer)peers.get(peerLoc));
        //Point p = new Point(packet.locationX, packet.locationY);
        //CellImpl cell = getCellImpl(p);
        //cell.setContents(null);
        
        RemoteClient newClient = new RemoteClient(packet.me.name);
        Point pNew = new Point(packet.locationXNew, packet.locationYNew);
		Direction dir = new Direction(packet.direction);
		maze.addClientAtGivenPoint(newClient, new DirectedPoint(pNew, dir));				
        clientList.put(packet.me, newClient);      
    }
    
 

    public void remove_remote_client(Peer peer)
    {        
        Client client = clientList.get(peer);
        if ( client != null)
        {
            System.out.println("Removing peer: " + peer.name);
            maze.removeClient(client);
            clientList.remove(peer);
            //peers.remove(peer);
        }
    }

    void broadcast_bye() throws Exception
    {
        GamePacket byePacket = new GamePacket();
        byePacket.MsgType = GamePacket.MessageType.BYE;
        byePacket.me = myself;
        for (int i = 0; i < peers.size(); i++)
        {
            Peer peer = (Peer)peers.get(i);

            if (peer == myself)
                continue;

            Socket socPeer = new Socket(peer.hostname, peer.port);
            ObjectOutputStream toPeer = new ObjectOutputStream(socPeer.getOutputStream());
            toPeer.writeObject(byePacket);
            toPeer.close();
            socPeer.close();
        }
    }

    /** 
         * The place where all the pieces are put together. 
         */
    public Mazewar(boolean isFirstClient)
    {
        super("ECE419 Mazewar");
        peers = new Vector(5);

        consolePrintLn("ECE419 Mazewar started!");
        myself = new Peer();
        

        clientList = new Hashtable<Peer, Client>();

        keepListening = true;
        new Listener(this).start();
        

                    // Create the maze
        maze = new MazeImpl(new Point(mazeWidth, mazeHeight), mazeSeed, peers);
        assert(maze != null);

        // Have the ScoreTableModel listen to the maze to find
        // out how to adjust scores.
        ScoreTableModel scoreModel = new ScoreTableModel();
        assert(scoreModel != null);
        maze.addMazeListener(scoreModel);

        // Throw up a dialog to get the GUIClient name.
        String name = JOptionPane.showInputDialog("Enter your name");
        if ((name == null) || (name.length() == 0))
        {
            clientName = name;
            try
            {
                keepListening = false;
                broadcast_bye();
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            System.exit(0);
            //quit();
        }

        myself.name = name;

        // Create the GUIClient and connect it to the KeyListener queue//
        guiClient = new GUIClient(name);
        maze.addClient(guiClient);
        this.addKeyListener(this);
		//System.out.println("Adding " + myself.name + " to list of peers.");
        peers.add(myself);
        clientList.put(myself, guiClient);

        // Use braces to force constructors not to be called at the beginning of the
        // constructor.
        /*
        {
                maze.addClient(new RobotClient("Norby"));
                maze.addClient(new RobotClient("Robbie"));
                maze.addClient(new RobotClient("Clango"));
                maze.addClient(new RobotClient("Marvin"));
        }
        */

        // Create the panel that will display the maze.
        overheadPanel = new OverheadMazePanel(maze, guiClient);
        assert(overheadPanel != null);
        maze.addMazeListener(overheadPanel);

        // Don't allow editing the console from the GUI
        console.setEditable(false);
        console.setFocusable(false);
        console.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder()));

        // Allow the console to scroll by putting it in a scrollpane
        JScrollPane consoleScrollPane = new JScrollPane(console);
        assert(consoleScrollPane != null);
        consoleScrollPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Console"));

        // Create the score table
        scoreTable = new JTable(scoreModel);
        assert(scoreTable != null);
        scoreTable.setFocusable(false);
        scoreTable.setRowSelectionAllowed(false);

        // Allow the score table to scroll too.
        JScrollPane scoreScrollPane = new JScrollPane(scoreTable);
        assert(scoreScrollPane != null);
        scoreScrollPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Scores"));

        // Create the layout manager
        GridBagLayout layout = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        getContentPane().setLayout(layout);

        // Define the constraints on the components.
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 3.0;
        c.gridwidth = GridBagConstraints.REMAINDER;
        layout.setConstraints(overheadPanel, c);
        c.gridwidth = GridBagConstraints.RELATIVE;
        c.weightx = 2.0;
        c.weighty = 1.0;
        layout.setConstraints(consoleScrollPane, c);
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.weightx = 1.0;
        layout.setConstraints(scoreScrollPane, c);

        // Add the components
        getContentPane().add(overheadPanel);
        getContentPane().add(consoleScrollPane);
        getContentPane().add(scoreScrollPane);

        // Pack everything neatly.
        pack();

        // Let the magic begin.
        setVisible(true);
        overheadPanel.repaint();
        this.requestFocusInWindow();

        this.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent e)
            {
                try
                {
                    keepListening = false;
                    broadcast_bye();
                }
                catch (Exception ex)
                {
                    ex.printStackTrace();
                }
                System.exit(0);
                //hide();
            }
        }
        );

        if (!isFirstClient)
            send_register_message();               
    }


    /**
         * Entry point for the game.  
         * @param args Command-line arguments.
         */
    public static void main(String args[])
    {     
        int isFirstClient = JOptionPane.showConfirmDialog(null, "Are you the first client?", "Mazewar Networked!", JOptionPane.YES_NO_OPTION);
        if (isFirstClient == JOptionPane.OK_OPTION)
        {

        }
        else
        {
            /*Store the server hostname and the server port number */
            hostName = JOptionPane.showInputDialog(null, "Please enter the hostname of a client:", "localhost", JOptionPane.QUESTION_MESSAGE);
            portNum = Integer.parseInt(JOptionPane.showInputDialog(null, "Port # of a client:", "localhost", JOptionPane.QUESTION_MESSAGE));        
        }

        /* Create the GUI */
        new Mazewar(isFirstClient == JOptionPane.OK_OPTION);
    }

    public void send_register_message()
    {
        try
        {            
            GamePacket packet = new GamePacket();
            packet.MsgType = GamePacket.MessageType.REQUEST_PEER_LIST;
            packet.me = myself;
            System.err.println("Sending register message to " + hostName + ":" + portNum);
            //System.err.println(loc.toString());
            Socket peerSoc = new Socket(hostName, portNum);
            ObjectOutputStream toPeer = new ObjectOutputStream(peerSoc.getOutputStream());
            toPeer.writeObject(packet);
            toPeer.close();
            peerSoc.close();
            
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }    
}



